/*
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *      
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *      
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 */

#ifndef _TRANSFORMATION_H
#define _TRANSFORMATION_H

#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
#include <cstring>
#include <cmath>
#include <cstring>
#include <vector>

#include "gType.h"

#define OUTPUT_PRECISION 3

#define ERROR_LINE_COUNT	"You can not do a tool transformation for one point."
#define ERROR_LINE_MET		"The line and circle don't have a common intersect."
#define ERROR_CIRCLES_MET	"Circles don't have a common intersect."
#define ERROR_EXCEED		"Capabilities of the tool transformation were exceed."

#define ASSIGN_VECTOR(a, b) ({ \
	(a)[0] = (b)[0]; \
	(a)[1] = (b)[1]; \
	})

#define LINEAR_INTERPOL(O, P, q, t) ({ \
	(O)[0] = (P)[0] + (q)[0] * t; \
	(O)[1] = (P)[1] + (q)[1] * t; \
	})
	
#define LINEAR_NORMAL_INTERPOL(O, P, q, t) ({ \
	(O)[0] = (P)[0] - (q)[1] * t; \
	(O)[1] = (P)[1] + (q)[0] * t; \
	})

#define COMPARE(g0, g1) ( \
	(g0)[0] - GAP < (g1)[0] && (g0)[0] + GAP > (g1)[0] && \
	(g0)[1] - GAP < (g1)[1] && (g0)[1] + GAP > (g1)[1] \
	)

#define DISTANCE(p0, p1) ( \
	sqrt(((p1)[0] - (p0)[0]) * ((p1)[0] - (p0)[0]) + \
		((p1)[1] - (p0)[1]) * ((p1)[1] - (p0)[1])) \
	)

#define DOT_PRODUCT(a, b)({ \
        (a)[0] * (b)[0] + (a)[1] * b[1]; \
        })

//! Základní třída matice.
/*!
 * Třída implementující čtvercovou matici s operací násobení.
*/
class matrix{

	private:
		//! Hodnota, kterou bude každý prvek čtvercové matice inicializován.
		static const float INITIAL_VALUE = 0.0;
	
		//! Ukazatel do paměti matice (dvourozměrné pole).
		float **M;
		//! Počet řádků nebo sloupců matice.
		unsigned len;
	
	public:
		//! Konstrukor čtvercové matice.
		/*!
		 * Pamět matice je alokována dynamicky, při zániku destruktor pamět 
		 * automaticky dealokuje. Každý prvek matice je inicializován hodnotou
		 * INITIAL_VALUE.
		 * \param m Počet řádků, nebo sloupců matice.
		 * \see INITIAL_VALUE, matrix(matrix &), ~matrix()
		*/
		matrix(unsigned);
		//! Kopírovací konstruktor.
		/*!
		 * Vytváří hlubokou kopii.
		*/
		matrix(matrix &);
		//! Destruktor.
		/*!
		 * Uvolní všechnu pamět alokovanou v konstruktoru.
		*/
		~matrix();
		
		//! Transponuje matici.
		matrix &transposed();
		//! Vynásobí matici maticí.
		/*!
		 * Počet řádků a počet sloupců obou matic musí být stejný.
		 * \see getLen()
		*/
		matrix &multiply(matrix &);
		//! Vytvoří kopii.
		matrix &operator=(matrix &);
		//! Vrací řádek matice.
		/*!
		 * Řádek matice představuje jednorozměrné pole. Prvky matice 
		 * lze měnit, nebo získávat stejným způsobem jako v dvojrozměrném
		 * poli.
		 * \param i Index řádku matice, i = 0, 1, ..., getlen() - 1.
		*/
		float *operator[](unsigned);
		//! Počet řádků nebo sloupců matice.
		unsigned getLen();

};

//! Pro učely ladění.
std::ostream &operator<<(std::ostream &, matrix &);

//! Rozšiřuje zakladní matici o transformace
/*!
* Jedná se o jednotkovou čtvercovou matici, násobenou z prava uvedenými
* tranformačními maticemi. Výsledná matice může být pak aplikovaná na
* bloky (gObject) a na kontejnery bloků (gObjectContainer).
*/
class transformation: public matrix{
	
	private:
		//! Dimenze prostoru
		static const unsigned DIMENSION = 2;
		//! Nasobení vektorem z leva [x, y, w].
		float *multiplyVector(float *);
		
	protected:
		//! Aplikace tranformační matice na gTypeGo.
		gTypeGo &multiplyGTypeGo(gTypeGo &go);
		
	public:
		//! Kontruktor transformační matice.
		/*!
		 * Vyplní hlavní diagonálu jedničkami.
		*/
		transformation();
		//! Translace.
		/*!
		 * Posunutí souřadnic o vektor [x, y].
		 * \param dx
		 * \param dy
		*/
		transformation &translation(float, float);
		//! Rotace.
		/*!
		 * Rotace o úhel alfa v radianech kolem počátku [x, y].
		 * \param angle Úhel v radianech.
		 * \param x Souřadnice počátku.
		 * \param y	Souřadnice počátku.
		*/
		transformation &rotation(float, float, float);
		//! Scaling.
		/*!
		 * Změna měřítka.
		 * \param sx Měřítko pro osu x 1:sx.
		 * \param sy Měřítko pro osu y 1:sy.
		 * \param x Souřadnice počátku.
		 * \param y	Souřadnice počátku.
		*/
		transformation &scaling(float, float, float, float);
		//! Nastaví jednotkovou matici.
		void clear();
		
		//! Aplikuje transformační matici na blok dat.
		gObject &apply(gObject &);
		//! Aplikuje transformační matici na kontejner bloků.
		gObjectContainer &apply(gObjectContainer &);
			
		
};

//! Transformace nástroje.
/*!
 * Provadí transformace nástroje, jako je změna jeho poloměru, nebo jeho
 * výšky. Je třeba zdůraznit, že není známa přesná velikost nástroje
 * , proto všechny operace provádíme na základě přírůstku. Přírustek
 * může být i záporný. Tranformace má za úkol dodržet předem daná pravidla.
 * Počet bodů před transformací a po tranformaci musí zůstat stejný, z tohoto
 * důvodu může nastat vyjímka algorithmError, která ukazuje na nepoužítelnost
 * transformace pro dané parametry. Tranformace se nejlépe hodí pro malé změny
 * poloměru nástroje. 
*/

class toolTransformation{
	
	//! Vyjímka vyhozená při transformaci.
	/*!
	* Může nastat při použití příliš vysoké změny poloměru nástroje, nebo
	* při použití transformace na nevhodný tvar. Pokud tata vyjímka nastane
	* transformace se zastaví a data použitá pro transformaci budou poškozená, 
	* z tohoto důvodu se doporučuje udělat kopii dat, na kterou poté budou aplikované
	* transformace.
	*/
	public:
		class algorithmError: public std::exception{
	
			private:
				//! Naznačuje důvod, proč došlo k chybě.
				char *report;
				
			public:
				algorithmError(const char *) throw();
				~algorithmError() throw();
				
				virtual const char *what() const throw();

		};
	
	private:
		//! Pro rozhodnutí zda dva flouty se sobě rovnají.
		static const float GAP = 0.001;
		//! K tomuto se radsi nevyjadrovat, podivat se na quadraticEquation.
		static const float MAGICAL_NUMBER = -0.05;
		
		//! Přírůstek poloměru.
		float toolRadius;
		//! Přírůstek výšky.
		float depth;
		//! True, pokud se má aplikovat přírustek i na typ TYPE_LINEAR_MOVE.
		bool flag;
		bool commonPoint;
		
		float m[2], n[2];
		gTypeGo *goPrev, *go, *goNext;
		std::vector<gTypeGo *> overlooked;
		
		void setGo() throw(algorithmError);
		void goLinearLinear();
		void goLinearArc() throw(algorithmError);
		void goArcLinear() throw(algorithmError);
		void goArcArc() throw(algorithmError);
        bool nearPoint(float *, float *, float *, float *);
        bool suitPoint(float *, float *, float *, float *);
		bool getNext(gObject &, gObject::iterator &iter);
		void setOverlooked();
		float getRadius(gTypeGo &, float *);
		void setZ(gTypeGo &);
		void makeCommonPoint(gObject &object) throw(algorithmError);
		bool isCommonPoint(gObject &) const;
	
	public:
		//! Průnik přímky a přímky.
		/*!
		 * Máme dvě přímky dané parametricky: přímka 1. -> P + p * t 
		 * přímka 2. -> N + n * t0.
		 * Pokud přímky nejsou linearně závislé, najde t, pro který
		 * mají společný bod.
		 * \param P Bod první přínky P[2].
		 * \param N Bod druhé přímky N[2].
		 * \param p	Směrový vektor první přímky p[2].
		 * \param n	Směrový vektor druhé přímky n[2].
		 * \return Vrací velikost t, pro které mají přímky společný bod.
		*/
		static float lineIntersect(float *, float *, float *, float *);
		//! Linearní závislost dvou vektorů.
		/*!
		 * Vrací true, pokud jsou vektory linearně nezávislé. Je dobré ho
		 * použít s metodou lineIntersect.
		 * \param p Vektor p[2].
		 * \param m Vektor m[2].
		*/
		static bool isLinearlyIndependent(float *, float *);
		//! Řešení kvadratické rovnice.
		/*!
		 * Používá se při výpočtu průsečíku kružnice s přímkou. Řeší
		 * kavadratickou rovnici ve tvaru a*x**2 + b*x + c = 0. Vrací počet
		 * řešení, které může nabývat hodnot 0, 1, 2.
		 * \param a
		 * \param b
		 * \param c
		 * \param x Do pole velikosti dva x[2] jsou umístěna řešení, v případě
		 * jednoho řešení se hodnota nachází v poli s indexem 0.
		 * \return Vrací počet řešení.
		*/
		static int quadraticEquation(float, float, float, float *);
		//! Průnik přímky a kružnice.
		/*!
		 * Máme kružnici -> (x - S[0])**2 + (y - S[1])**2 = r**2 a 
		 * přímku -> (x, y) = P + p * t. Hledáne jejich společné
		 * body (x, y).
		 * \param P Bod přímky P[2].
		 * \param p Směrový vektor přímky p[2].
		 * \param S Bod středu kružnice S[2].
		 * \param r Poloměr kružnice.
		 * \param t Do pole velikosti dva jsou umístěna řešení, v případě
		 * jednoho řešení (přímka je tečna) se hodnota nachází v poli s indexem 0.
		 * \return Vrací počet řešení.
		*/
		static int lineArcIntersect(float *, float *, float *, float, float *);
		//! Průnik kružnice a kružnice.
		/*!
		 * Máme kružnici 1. -> (x - S0[0])**2 + (y - S0[1])**2 = r0**2 a 
		 * ružnici 2. -> (x - S1[0])**2 + (y - S1[1])**2 = r1**2. Hledáne jejich společné
		 * body (x, y). Vrací počet řešení a neošetřuje možnost nekonečno mnoho řešení. 
		 * \param S0 Střed první kružnice S0[2].
		 * \param r0 Poloměr první kružnice r0[2].
		 * \param S1 Střed druhé kružnice S1[2].
		 * \param r1 Poloměr druhé kružnice r0[2]
		 * \param P0 První společný bod P0[2].
		 * \param P1 Druhý společný bod P1[2].
		 * \return Vrací počet řešení, v případě jen jednoho řešení, je uloženo v bodu P0.
		*/
		static int arcIntersect(float *, float, float *, float, float *, float *);
		//! Úhel vektorů.
		/*!
		 * Změří úhel v kružnici. Úhel je měřen v radianech v intervalu
		 * <0, 2PI>.
		 * \param S Střed kružnice S[2].
		 * \param P Bod na kruřnici P[2].
		 * \return Vrací úhel.
		*/
		static float arcAngle(float *, float *);
		//! Norma vektoru.
		/*!
		 * Z daných bodů se určí normovaný vektor(vektor o velikosti |v| = 1).
		 * \param M První bod M[2].
		 * \param N Druhý bod N[2].
		 * \param v Normovaný vektor v[2].
		*/
		static void getUnitVector(float *, float *, float *);
		
		//! Konstruktor transformace nástroje.
		/*!
		 * Nastaví přírusky na 0 a flag na false.
		*/
		toolTransformation();
		
		//! Nastaví parametr nástroje a výšku.
		/*!
		 * Parametr nástroje se vždy uvádí jako rozdíl mezi původní velikostí 
		 * nástoje a novou velikostí, tedy původní velikost - nová velikost.
		 * Výška se taktéž uvádí jako rozdíl mezi původní výškou a novou 
		 * výškou.
		 * \param r Přírustek nástroje.
		 * \param z Přířůstek k výšce.
		*/
		void setParam(float, float);
		//! Aplikovat výšku na rychlo posuvy.
		/*!
		 * Pokud je nastaveno true, přírůstek výšky je aplikován i
		 * na rychloposuvy G0.
		*/
		void setMoves(bool);
		//! Nastavení aplikace na rychloposuv.
		bool getMoves() const;
		//! Společný koncový bod
		/*!
		 * Pokud první a poslední bod se nachází na stejných souřadnicích
		 * (x, y) je mezi nimi spočítán průsečík, za předpokladu že je 
		 * nastaveno true.
		*/
		void setCommonPoint(bool);
		//! Vrací status společného bodu
		bool getCommonPoint() const;
		//! Vrací přírůstek nástroje.
		float getToolRadius() const;
		//! Vrací přírůstek výšky.
		float getDepth() const;
		
		//! Aplikuje transformace na objekt.
		/*!
		 * Při aplikaci transformací může dojít k vyjímce a objekt může
		 * zustat poškozený, z tohoto důvodu je bezpečné používat kopii
		 * původního objektu.
		 * \param object Objekt.
		*/
		gObject &apply(gObject &) throw(algorithmError);
		//! Aplikuje transformace na kontejner.
		/*!
		 * Při aplikaci transformací může dojít k vyjímce a objekt může
		 * zustat poškozený, z tohoto důvodu je bezpečné používat kopii
		 * původního kontejneru.
		 * \param container Kontejner.
		*/
                gObjectContainer &apply(gObjectContainer &) throw(algorithmError);

};

class morphTransformation: public transformation{
	
	public:
                class morphError: public toolTransformation::algorithmError{

                    public:
                    morphError(const char *str): toolTransformation::algorithmError(str){};

                };
		
		enum morphType{DISTANCE, MOVE};
	
	private:
		static const float GAP = 0.001;
	
		float distance;
		float point[2];
		gObject *object;
		gTypeGo *go;
		gObject::iterator iterPrev, iter, iterNext;
		morphType type;
		bool next, commonPoint;
		std::vector<gTypeGo *> group;
		
		void movePrev();
		void moveNext();
		gObject::iterator getPrev();
		gObject::iterator getNext();
		void interpolPoint(float *, float *, int);
		void assignVector(float *);
		
		bool isLinear(gTypeGo &) const;
		bool isArc(gTypeGo &) const;
		void transformDistance() throw(morphError);
		void transformMove() throw(morphError);
		
	public:
		morphTransformation();
		
		void setType(morphType);
		void setDistance(float);
		void setMove(float, float);
		void setNext(bool);
		void setCommonPoint(bool);
		
		bool isCommonPoint() const;
		morphType getType() const;
		bool isNext() const;
		float getDistance() const;
		void getPoint(float *) const;
		
		void apply(gObject *, gTypeGo *) throw(morphError);

};

#endif
